/* Box blur (simple smooth) problem optimized using motion blur. June 2014
 * Felipe Osorio Thome 7696409 <f.o.thome@gmail.com>.
 * Thales Andrade 7573527.
 * Vitor Lima 7573361. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include "pbmpak.h"

#define DEBUG

#define KERNEL_LENGTH 5

FILE *xfopen(char *, char *);
void *xcalloc(unsigned long int, int);
int *box_blur(int, int, int *);

typedef struct
{
  int xsize;
  int ysize;
  int maxrgb;
} IMG_HEADER;

typedef struct
{
  int *rarray;
  int *garray;
  int *barray;
} IMG_DATA;

int dim_kernel;
int half;

int main(int argc, char *argv[])
{
  /* Problem related variables. */
  FILE *img = NULL;
  IMG_HEADER header;
  IMG_DATA datain, dataout;
  char filename_in[36], filename_out[36];
  int size = 0;

  /* Initializing structs with "null" data. */
  memset(&header, 0, sizeof(IMG_HEADER));
  memset(&datain, 0, sizeof(IMG_DATA));

  /* This block is just for debug. */
  if(argc > 3)
  {
    strcpy(filename_in, argv[1]);
    strcpy(filename_out, argv[2]);

    dim_kernel = atoi(argv[3]);
    half = dim_kernel / 2;
  }
  else
  {
    strcpy(filename_in, "in.ppm");
    strcpy(filename_out, "out.ppm");

    dim_kernel = KERNEL_LENGTH;
    half = dim_kernel / 2;
  }

  img = xfopen(filename_in, "r");

  /* Reading ppm image header. */
  ppma_read_header(img, &(header.xsize), &(header.ysize), &(header.maxrgb));

  /* Allocating data input arrays. */
  size = header.xsize * header.ysize;
  datain.rarray = xcalloc(size, sizeof(int));
  datain.garray = xcalloc(size, sizeof(int));
  datain.barray = xcalloc(size, sizeof(int));

  /* Reading ppm image data */
  ppma_read_data(img, header.xsize, header.ysize, datain.rarray, datain.garray, datain.barray);

  /* Calculating box blur. */
  #ifdef DEBUG
    struct timeval t1, t2;
    double elapsedTime;
    gettimeofday(&t1, NULL);
  #endif

  dataout.rarray = box_blur(header.xsize, header.ysize, datain.rarray);
  dataout.garray = box_blur(header.xsize, header.ysize, datain.garray);
  dataout.barray = box_blur(header.xsize, header.ysize, datain.barray);

  #ifdef DEBUG
    gettimeofday(&t2, NULL);
    elapsedTime = (t2.tv_sec - t1.tv_sec) * 1000.0;
    elapsedTime += (t2.tv_usec - t1.tv_usec) / 1000.0;
    printf("computation time(ms): %.2lf \n", elapsedTime);
  #endif

  /* Writing the output file. */
  ppma_write(filename_out, header.xsize, header.ysize, header.maxrgb,
             dataout.rarray, dataout.garray, dataout.barray);

  /* Freeing memory. */
  fclose(img);

  free(datain.rarray);
  free(datain.garray);
  free(datain.barray);

  free(dataout.rarray);
  free(dataout.garray);
  free(dataout.barray);

  return 0;
}

int *box_blur(int xsize, int ysize, int *array)
{
  /* The box blur can be optimized using motion blur.
       First we apply horizontal motion blur and then
       we apply vertical motion blur. The result is equivalent.

       As a second optimization we can reuse some calculations.
       Actually just the first pixel of each row (or column) need
       to be computed using the classical form. For others only changes
       the left (top) most pixel and right (bottom) most pixel. */

  int *accumulation = NULL, *result_temp = NULL, *result = NULL;
  int i, j, left_most_pixel, right_most_pixel, top_most_pixel, bottom_most_pixel;

  result_temp = xcalloc(xsize * ysize, sizeof(int));
  result = xcalloc(xsize * ysize, sizeof(int));
  accumulation = xcalloc(xsize, sizeof(int));

  /* Horizontal motion blur. */
  for(i = 0; i < ysize; i++)
  {
    accumulation[0] = 0;

    /* First pixel of each row. */
    for(j = 0; j <= half && j < xsize; j++)
    {
      accumulation[0] += array[i * xsize + j];
    }
    result_temp[i * xsize] = accumulation[0] / dim_kernel;

    /* Middle pixels of each row. */
    for(j = 1; j < (xsize - 1); j++)
    {
      left_most_pixel = (j - half - 1) < 0 ? 0 : array[i * xsize + (j - half - 1)];
      right_most_pixel = (j + half) >= xsize ? 0 : array[i * xsize + (j + half)];

      accumulation[j] = accumulation[j - 1] - left_most_pixel + right_most_pixel;

      result_temp[i * xsize + j] = accumulation[j] / dim_kernel;
    }

    /* Last pixel of each row. */
    left_most_pixel = (j - half - 1) < 0 ? 0 :  array[i * xsize + (j - half - 1)];
    accumulation[j] = accumulation[j - 1] - left_most_pixel;
    result_temp[i * xsize + j] = accumulation[j] / dim_kernel;
  }

  /* Vertical motion blur. */
  for(i = 0; i < xsize; i++)
  {
    accumulation[0] = 0;

    /* First pixel of each column. */
    for(j = 0; j <= half && j < ysize; j++)
    {
      accumulation[0] += result_temp[j * xsize + i];
    }
    result[i] = accumulation[0] / dim_kernel;

    /* Middle pixels of each column. */
    for(j = 1; j < (ysize - 1); j++)
    {
      top_most_pixel = (j - half - 1) < 0 ? 0 : result_temp[(j - half - 1) * xsize + i];
      bottom_most_pixel = (j + half) >= ysize ? 0 : result_temp[(j + half) * xsize + i];

      accumulation[j] = accumulation[j - 1] - top_most_pixel + bottom_most_pixel;

      result[j * xsize + i] = accumulation[j] / dim_kernel;
    }

    /* Last pixel of each column. */
    top_most_pixel = (j - half - 1) < 0 ? 0 :  result_temp[(j - half - 1) * xsize + i];
    accumulation[j] = accumulation[j - 1] - top_most_pixel;
    result[j * xsize + i] = accumulation[j] / dim_kernel;
  }

  free(result_temp);
  free(accumulation);

  return result;
}

FILE *xfopen(char *filename, char *mode)
{
  FILE *ptr = NULL;

  ptr = fopen(filename, mode);

  if(!ptr)
  {
    fprintf(stderr, "%s: %d: %s\n", __FILE__, __LINE__, strerror(errno));
    exit(EXIT_FAILURE);
  }

  return ptr;
}

void *xcalloc(unsigned long int num, int size)
{
  void *ptr = NULL;

  ptr = calloc(num, size);

  if(!ptr)
  {
    fprintf(stderr, "%s: %d: %s\n", __FILE__, __LINE__, strerror(errno));
    exit(EXIT_FAILURE);
  }

  return ptr;
}
